// Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.

// 监控任务
package metricmonitor

import (
	"fmt"
	event "scase.io/application-auto-scaling-service/pkg/service/event"
	"time"

	"scase.io/application-auto-scaling-service/pkg/common"
	"scase.io/application-auto-scaling-service/pkg/db"
	"scase.io/application-auto-scaling-service/pkg/metricmonitor/influxdb"
	"scase.io/application-auto-scaling-service/pkg/metricmonitor/metric"
	"scase.io/application-auto-scaling-service/pkg/metricmonitor/model"
	"scase.io/application-auto-scaling-service/pkg/service/taskservice"
	"scase.io/application-auto-scaling-service/pkg/utils/logger"
)

// targetBasedMonitorTask 基于目标的监控任务
// 1.当实例伸缩组不处于active或enableAutoScaling状态时，不执行自动伸缩；
// 2.当实例伸缩组处于冷却期间时，不执行自动伸缩；
// 3.当实例伸缩组无伸缩伸缩时，不执行伸缩决策；
func targetBasedMonitorTask(log *logger.FMLogger, influxCtr *influxdb.Controller, task *db.MetricMonitorTask) {
	group := getEnableScalingGroup(log, "", task.ScalingGroupID, task.ScalingPolicyID)
	if group == nil {
		return
	}

	metricData, err := metric.GetMetricData(log, influxCtr, group, task.MaxTargetValue, task.MinTargetValue)
	if err != nil {
		log.Error("get metric data of group [%s] error:%+v", group.Id, err)
		return
	}
	var curActiveNum int
	var decision *model.ScalingDecision
	if group.InstanceType == common.InstanceTypeVM {
		_, curActiveNum, _, err = db.EcsInfoTable().ListInstance(group.Id, db.EcsStateActive, 0, -1)
		if err != nil {
			log.Error("get current active instance num of group [%s] error:%+v", group.Id, err)
			curActiveNum = int(group.CurrentInstanceNumber)
		}
	} else if group.InstanceType == common.InstanceTypePod {
		_, curActiveNum, _, err = db.PodInfoTable().ListPod(group.Id, db.EcsStateActive, 0, -1)
		if err != nil {
			log.Error("get current active instance num of group [%s] error:%+v", group.Id, err)
			curActiveNum = int(group.CurrentInstanceNumber)
		}
	} else {
		log.Error("invalid instance type: %s", group.InstanceType)
		return
	}
	decision, err = metric.ScalingDecisionByAvailableServerSessionsPercentOfGroup(log, influxCtr,
		group, int32(curActiveNum), metricData)
	if err != nil {
		log.Error(err.Error())
		return
	}
	if decision == nil {
		log.Warn(fmt.Sprintf("No monitoring data of ScalingGroup[%s] in influx", group.Id))
		return
	}
	if decision.Action == model.ScalingDecisionActionNone {
		return
	}
	if err := handleDecision(log, group, decision, metricData); err != nil {
		log.Error("handle decision failed with error:%+v", err)
		return
	}
	if err := db.UpdateScalingGroup(group, "auto_scaling_timestamp", "desire_instance_number"); err != nil {
		log.Error("Update AutoScalingTimestamp of ScalingGroup[%s] is failed, err: %+v", err)
		return
	}
}

func handleDecision(log *logger.FMLogger, group *db.ScalingGroup, decision *model.ScalingDecision, metricData *metric.ServerSessions) error {
	var eventName string
	eventService, err := event.NewEventService(group.ProjectId, nil, log)
	if err != nil {
		log.Error("new event service error:%+v", err)
		return err
	}
	if decision.Action == model.ScalingDecisionActionIn {
		// 缩容需要校验冷却时间，伸缩组状态
		if !AllowScaleIn(log, group) {
			return fmt.Errorf("scaling group [%s] not allow scale in", group.Id)
		}
		err = taskservice.StartEcsScaleInGroupTask(group, decision.Instances)
		eventName = "scale-in"
	} else if decision.Action == model.ScalingDecisionActionOut {
		err = taskservice.StartEcsScalingOutTask(group, group.DesireInstanceNumber)
		eventName = "scale-out"
	}
	if err != nil {
		log.Error("Start scaling task for group[%s] failed, err: %s", group.Id, err.Error())
		return err
	}
	msg := fmt.Sprintf("fleet [%s] auto scale success, action: %s, available percent: %.2f, max: %.2f, min:%.2f ,target instance num: %d",
		group.FleetId, decision.Action, metricData.AvailablePercentOfGroup*1e2, metricData.TargetMaxPercent*1e2, metricData.TargetMinPercent*1e2, group.DesireInstanceNumber)
	eveReq := eventService.BuildInstanceEventReq(fmt.Sprintf("[auto-%s]", eventName), group.Id, msg, msg,
		common.EventTraceStateNormal)
	eventService.RegisterEvent(eveReq, group.ProjectId, log)
	group.AutoScalingTimestamp = time.Now().UnixNano()
	return nil
}

func getEnableScalingGroup(log *logger.FMLogger, projectId, groupId, policyId string) *db.ScalingGroup {
	group, err := db.GetScalingGroupById(projectId, groupId)
	if err != nil {
		log.Error(fmt.Sprintf("it's failed to query ScalingGroup[%s] of policy[%s] from db", groupId, policyId))
		return nil
	}
	if group.EnableAutoScaling == false {
		log.Info(fmt.Sprintf("ScalingGroup[%s] is not enableAutoScaling", group.Id))
		return nil
	}
	return group
}

// 缩容条件判决
func AllowScaleIn(log *logger.FMLogger, group *db.ScalingGroup) bool {
	if group.State != db.ScalingGroupStateStable {
		log.Info(fmt.Sprintf("ScalingGroup[%s] is not stable ", group.Id))
		return false
	}
	if time.Now().UnixNano()-group.AutoScalingTimestamp < group.CoolDownTime*int64(time.Minute) {
		endTime := time.Unix(0, group.AutoScalingTimestamp+group.CoolDownTime*int64(time.Minute)).Format(common.TimeLayout)
		logger.R.Info("ScalingGroup[%s] is in the cooling duration until %s", group.Id, endTime)
		return false
	}
	return true
}
